This Technical Note discusses how to install a driver to have more than one volume on a 3.5" 800K disk under Apple II Pascal.
For the sake of simplicity, we will limit the discussion to the following case: we want to have two 400K volumes on the boot 3.5" disk. For such a scenario, Unit #4 occupies the first 800 blocks and Unit #20 uses blocks 800 to 1599 as shown here:
First Volume Unit #4 Second Volume Unit #4
__|_________________________________|___________________________________|__
| Blocks (0 .. 799) | Blocks (800 .. 1599) |
+-- Directory Unit #4 +-- Directory Unit #20
| blocks (2 .. 5) | blocks (802 .. 805)
| |
___________________________________________________________________________
| | | | | | |
|_|___|_____________________________|_|___|_____________________________|__
| \ | \
| \ Boot Blocks (0 .. 1) | \ Pseudo Boot Blocks (800 .. 801)
Figure 1 - Block Diagram for 3.5" Disk
There are four calls a device driver has to handle, UNITCLEAR, UNITSTATUS, UNITREAD, and UNITWRITE. For the first one, our driver will only return since the device is already on-line. For a blocked device, UNITSTATUS returns the number of blocks available, in this case UNITSTATUS (20) = 800.
In the case of UNITREAD and UNITWRITE, all the driver has to do is add the offset of 800 to the number of the block requested then jump to the BIOS routine with the unit number set to four. Our driver is basically a dispatcher that directs the disk access to the proper blocks.
When this driver is present, the application must be very careful about making sure the right disk is in the drive when accessing the second volume; any access to Unit #20 could damage a normal volume present in the drive.
Once the driver is ready, it is necessary to format a disk with the special directories. With the listings for the driver we have included the source of a sample formatting program.
Once the disk is ready we proceed to transfer all system files to it including SYSTEM.ATTACH, ATTACH.DRIVERS (containing our driver), and ATTACH.DATA. This last file reflects the following information:
Driver Name - FAKEDISK - Not Aligned
Attached to #20 {Can change if desired}
Unit #s to be init at boot time - 20
This driver CAN be placed in the first HiRes screen {Change if needed}
This driver CAN be placed in the second HiRes screen{Change if needed}
This driver does not use interrupts
Driver does not have transient initialization code
The code has comments that explain it fairly well; for more information on drivers in general and how to use the attach tools please refer to Apple II Pascal Device and Interrupt Support Tools.
;
; Disk Driver
; by Guillermo Ortiz
; 03/25/86
;
; This driver will allow splitting a 3.5 disk in two pieces of 400K
; each, therefore permitting more than 77 files per disk. It
; is required to "format" the disk with two directories, one at
; block 0 .. 5 and the other at block 800 .. 805, each with a
; length of 800 blocks. Names must be different!
; The ancient admonition:
;
; This is a sample!
; No claims are made regarding the fitness of this code for
; any particular purpose.
ROUTINE .EQU 02 ; For indirect jumping
RETURN .EQU 04 ; Back to Pascal
BUFF .EQU 06 ; Where to put stuff
.PROC FAKEDISK
; At this level we could have some code to differentiate
; between different pseudo volumes if we had more than
; two pseudo-volumes per disk.
; In this example we use Unit # 20 for the second part.
; Using units 13 and up let us keep the "standard" drives available
; In any UNIT call X Register contains the type of call
; as follows:
CPX #04
BEQ STATUS ; X = 4
CPX #02
BEQ INIT ; X = 2
STA TEMP1
STY TEMP1+1 ; Saving A, Y and X
STX TEMP1+2 ; for future use
; We make the assumption that the disk split is the
; System Volume, so we get the logical volume number for
; Unit # 4 from the DISKNUM table;
; see Apple // Pascal Device and Interrupt
; Support Tools manual for details.
TSX ; Gimmie the stack pointer
LDA 0FEB6 ; Logical volume for boot disk
STA 109,X ; so read from that disk
; Our fiddling is complete now let's finish checking
; the call in order to make the jump
LDA TEMP1+2 ; X contains the call code
BEQ READ ; X = 0
CMP #01
BEQ WRITE ; X = 1
; Here we could have
; instructions to report some undefined control code.
; This driver will only CRASH!!!
BRK ; Bumm!!!
; Now the real stuff
READ .EQU *
JSR SETUP ; Modify the stack
LDY #19. ; Index for Reading from disk
BNE GET ; Nice way of jumping
WRITE .EQU *
JSR SETUP ; Modify the stack
LDY #16. ; Index for WRITE to CONSOLE
GET LDA @0E2,Y ; $E2 contains a pointer to the jump vector
STA ROUTINE ; Set low byte of address
INY
LDA @0E2,Y ; Get high byte of address
STA ROUTINE+1 ; and set it off
LDX TEMP1+2 ; Restore
LDY TEMP1+1 ; all registers
LDA TEMP1 ; before jump
JMP @ROUTINE ; and Go!
; INIT will only pass back the no_error IORESULT
INIT .EQU *
LDX #00 ; No error
RTS ; Go back
STATUS PLA ; Get
STA RETURN ; return
PLA ; address
STA RETURN+1
PLA ; Get
STA BUFF ; Pascal
PLA ; Buffer
STA BUFF+1 ; address
PLA ; Dump control
PLA ; word
LDY #00
LDA #20 ; Set
STA @BUFF,Y ; the number of blocks
INY ; to
LDA #03 ; 800
STA @BUFF,Y
LDX #00
LDA RETURN+1 ; and
PHA
LDA RETURN
PHA
RTS ; Return!
; To any request for READ/WRITE we'll add 800 to the
; number of the block needed.
SETUP .EQU *
LDA 103,X ; Get Block number low
CLC ; Set up for addition
ADC #20 ; Offset block count by 800
STA 103,X ; and restore
LDA 104,X ; Get Block number high
ADC #03 ; 800 = $320
STA 104,X ; and restore
RTS ; Go back
TEMP1 .BLOCK 3 ; Temporary storage area
.END
The driver requires that the disk be formatted in a special way. Run the following program to create your volume.
program REFORMAT;
{By Guillermo Ortiz
03/27/86
}
{This program takes a newly formatted 3.5 disk and lays down two
directories transforming the volume into two 400K pseudo-volumes to be
used with the driver FAKEDISK which assigns Unit # 20 to the second
part of the disk.
}
CONST MAXDIR = 77; {Max number of files per volume}
VIDLENGTH = 7; {Max chars in volume name}
TIDLENGTH = 15; {Max chars per file ID}
FBLKSIZE = 512; {Number of bytes per block}
DIRBLK = 2; {We are reading the directory}
type daterec = packed record
month:0..12; {0 --> Meaningless date}
day: 0..31; {Day of month}
year:0..100 {100 --> dated volume is temp}
end;
vid = string [vidlength]; {Volume ID}
dirrange = 0 .. maxdir; {Number of files on disk}
tid = string[tidlength]; {File ID}
filekind = (untypedfile,xdskfile,codefile,textfile,infofile,
datafile,graffile,fotofile,securdir);
{Now the real directory layout}
direntry =
packed record
dfirstblk:integer; {1st physical disk address}
dlastblock:integer; {block after last used block}
case dfkind:filekind of
securdir,untypedfile: {Volume info only in dir[0]}
(filler1: 0..2048; {Waste 13 bits}
dvid: vid; {Name of volume}
deovblk: integer; {Last block in volume}
dnumfiles:dirrange; {Number of files in directory}
dloadtime:integer; {Time of last access}
dlastboot:daterec); {Most recent date setting}
xdskfile,codefile,textfile,infofile,datafile,
graffile,fotofile: {Regular file info}
(filler2: 0..1024; {Waste 12 bits}
status: boolean; {For filer wildcards}
dtid: tid; {Name of file}
dlastbyte:1..fblksize; {Bytes in last block of file}
daccess: daterec) {Date of last modification}
end; {Of the whole directory record}
directory = array [dirrange] of direntry;
var dirinfo:directory; {The directory goes here}
UNITNUM:INTEGER;
CH:CHAR;
PROCEDURE DOSTUFF;
{Function CHECK will read the directory from a freshly formatted
3.5 disk, then DOSTUFF will make changes so it has only 800 blocks and
a name HALFONE: and will write it back to block 2; then we will
change the name to HALFTWO: and will write to block 802 as
the directory for our second pseudo-volume.
}
BEGIN
with dirinfo[0] do
begin
deovblk:=800; {Cut it in half}
dvid:='HALFONE';
end;
unitwrite(UNITNUM,dirinfo,sizeof(dirinfo),dirblk); {Put back main directory}
DIRINFO[0].DVID:='HALFTWO';
unitwrite(UNITNUM,dirinfo,sizeof(dirinfo),dirblk+800) {Write second dir.}
end; {Of DOSTUFF}
FUNCTION CHECK:BOOLEAN;
{Reads the directory from the target disk, if possible, warns the user
of the certain destruction of the current directory and checks the
size of the volume so that the program doesn't use other than 3.5
disks.
}
BEGIN
CHECK:=FALSE;
DIRINFO[0].DLASTBLOCK:=-999; {Make sure we read from a disk}
UNITREAD(UNITNUM,DIRINFO,SIZEOF(DIRINFO),DIRBLK);
IF DIRINFO[0].DLASTBLOCK= 6 THEN {IS THIS A PASCAL DISK?}
BEGIN
IF DIRINFO[0].DEOVBLK <> 1600 THEN
BEGIN
WRITELN('SORRY THIS PROGRAM IS INTENDED FOR 3.5 DISKS ONLY');
EXIT(CHECK)
END;
WRITE('WE ARE ABOUT TO PERMANENTLY DESTROY ');
WRITELN(DIRINFO[0].DVID,':');
WRITE('IS IT OK? --> ');
REPEAT
READ(KEYBOARD,CH)
UNTIL CH IN ['Y','N','n','y'];
WRITELN(CH);
IF CH IN ['Y','y'] THEN
CHECK:=TRUE
END
ELSE
BEGIN
WRITELN;
WRITELN;
WRITELN('CAN NOT READ DIRECTORY')
END
END {OF CHECK};
PROCEDURE GETNUM;
{Prompts the user for the Unit Number of the target disk,
checks the validity of the input and returns when provided with
a reasonable value.
}
VAR I:INTEGER;
BEGIN
WRITELN;
WRITELN('PLEASE ENTER THE NUMBER OF THE UNIT CONTAINING THE DISK');
WRITE('TO BE REFORMATTED (PRESS TO EXIT) --> ');
UNITNUM:=0;
REPEAT
BEGIN
WRITE(CHR(5)); {Cursor ON}
READ(CH); {For the prompt}
WRITE(CHR(6)); {and then OFF for speed and elegance(?)}
IF EOLN THEN
IF (UNITNUM IN [4,5,9..12]) THEN
EXIT(GETNUM)
ELSE
FOR I:= 1 TO 32 - UNITNUM DO {Kind of crude but ...}
WRITE(CHR(8)); {to go back to the same place}
IF ORD(CH) = 27 THEN
BEGIN
WRITELN;
WRITELN('YOU ASKED FOR IT!!!');
WRITE(CHR(5)); {Turn cursor ON before we exit}
EXIT(PROGRAM)
END;
IF (ORD(CH) = 8) AND (UNITNUM > 0) THEN
BEGIN
IF UNITNUM < 10 THEN
UNITNUM:=0
ELSE
UNITNUM:=UNITNUM DIV 10;
WRITE(CHR(8),' ',CHR(8)) {To delete previous entry}
END
ELSE
BEGIN
IF (UNITNUM = 0) AND (CH IN ['1','4','5','9']) THEN
UNITNUM:=ORD(CH)-ORD('0')
ELSE
IF (UNITNUM=1) AND (CH IN ['0','1','2']) THEN
UNITNUM:=10*UNITNUM+ORD(CH)-ORD('0')
ELSE
IF ORD(CH) > 31 THEN
WRITE(CHR(8),' ',CHR(8)) {Unwanted stuff,so ...}
END {get rid of it. }
END
UNTIL FALSE; {No Exit here.}
WRITELN
END {OF GETNUM};
BEGIN {main}
WRITELN;
WRITELN;
WRITELN('WE ARE ABOUT TO REFORMAT A VOLUME SO IT WILL CONTAIN TWO');
WRITELN('400K PSEUDO-VOLUMES. MAKE SURE YOU MARK THE DISK CLEARLY');
WRITELN('SO YOU DON''T FORGET');
WRITELN;
WRITELN;
REPEAT
GETNUM
UNTIL CHECK;
DOSTUFF;
WRITE(CHR(5)); {Don't forget to turn cursor ON}
writeln;
WRITELN('AWAAAAAY!!!')
end.
If two volumes are not enough, you can modify this example to support more than two per disk; the key is to keep in mind that when the call comes to the driver, the accumulator contains the number of the Unit the for which the call is intended. After checking this number the driver could decide what offset it has to add to access the correct volume.
Of course the formatter program would have to change accordingly, laying down the directories for the new volumes with the appropriate names and sizes.
The same scheme can be applied to any device that Pascal can directly recognize (i.e., the Apple Memory Expansion Card, ProFile hard disk, etc.).
This and all of the other Apple II Technical Notes have been converted to HTML by Aaron Heiss as a public service to the Apple II community, with permission by Apple Computer, Inc. Any and all trademarks, registered and otherwise, are properties of their owners.